/*** file operations funcitons ****/

static unsigned int lcdmod_read_open=0, lcdmod_write_open=0; // times opened

/* input_state states */
enum {
	NORMAL,
	ESC_,		 /* the [ in escape sequence */
	ESC,			// Escape sequence start
	DCA_Y,			// Direct cursor access, the next input will be the row
	DCA_X,			// Direct cursor access, the next input will be the column
	CGRAM_SELECT_ALL,	 // Selecting which slot to enter new character to all lcds
	CGRAM_GENERATE_ALL,	 // Waiting fot the 8 bytes which define a character for all lcds and flushing them
	CGRAM_SELECT_ONE,	// Selecting which slot to enter a new character and selecting lcd
	CGRAM_SELECT_LCD_ONE, /* selectging which lcd to flash new cgram, input is in unsigned int!!! */
	CGRAM_GENERATE_ONE, /* waiting for the 8 bytes which define a character and flushing them */
	CHAR_BYPAS,	/* write character directly to the LCD without remaping or interpretation */
	INST_BYPAS	// write instruction directly
};

/* Handle write to device file */
static void input_escape_sequence( unsigned char input )
{
	static unsigned int cgram_index = 0;
	static unsigned int cgram_row_count = 0;
	static unsigned int cgram_lcd = 0;
	static unsigned char cgram_pixels[ 8 ] = {[0 ... 7] = 0};
	static int input_state = NORMAL;	// the current state of the input handler
	int i, j;
	
	switch ( input_state ) {
	case NORMAL:
		switch ( input ) {
		case 0x08:	// Backspace
			if ( col ) {
				col--;
			}
			write_DDRAM(row, col, ' ' );
			break;
		case 0x09:	// Tabstop
			col = ( ( ( col + 1 ) / TABSTOP ) * TABSTOP ) + TABSTOP - 1;
			break;
		case 0x0a: /* new line, we have still free lines */
			if (row < DISP_ROWS - 1) {
				++row;
			} else { /* no moere free lines, scroll up */
				for ( i = 0; i < row; i++ ) {
					for( j = 0; j < DISP_COLS; j++ ) {
						write_DDRAM(i, j, state[i + 1][j] );
					}
				}
				for( i = 0; i < DISP_COLS; i++ ){
					write_DDRAM(row, i, charmap[' ']);
				}
			}
			/*break; Since many have trouble grasping the \r\n concept :) */
		case 0x0d:  // Carrage return
			col = 0;
			break;
		case 0x1b:  // esc ie. start of escape sequence
			input_state = ESC_;
			break;
		default:  // The character is looked up in the charmap[]
			global_write_DDRAM( charmap[ input ] );
		} /* switch (input) */
		break;
	case ESC_:
		input_state = ESC;
		break;
	case ESC:
		switch ( input ){
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':   // Chararacter from CGRAM
			global_write_DDRAM( input - '0' );
			input_state = NORMAL;
			break;
		case 'A':     // Cursor up
			if ( row )
				row--;
			input_state = NORMAL;
			break;
		case 'B':     // Cursor down
			if ( row < DISP_ROWS - 1 )
				row++;
			input_state = NORMAL;
			break;
		case 'C':     // Cursor Right
			if ( col < DISP_COLS - 1 )
			    col++;
			input_state = NORMAL;
			break;
		case 'D':     // Cursor Left
			if ( col )
				col--;
			input_state = NORMAL;
			break;
		case 'H':     // Cursor home
			row = 0;
			col = 0;
			input_state = NORMAL;
			break;
		case 'J':   // Clear screen, cursor doesn't move
			memset(state, ' ', sizeof(**state)*DISP_COLS*DISP_ROWS );
			write_inst_all(HD44780_CLRDISP);
			input_state = NORMAL;
			break;
		case 'K':     // Erase to end of line, cursor doesn't move
			for (i = col; i < DISP_COLS; ++i)
				write_DDRAM(row, i, charmap[' ']);
			input_state = NORMAL;
			break;
		case 'Y':     // Direct cursor access
			input_state = DCA_Y;
			break;
		case 'R':   // CGRAM select for all lcds
			input_state = CGRAM_SELECT_ALL;
			break;
		case 'r':   // CGRAM select for one lcd
			input_state = CGRAM_SELECT_ONE;
			break;
		case 'V':   // Linewrap on
			clear_bit(FLAG_WRAP_OFF, &(lcddev.flags));
			input_state = NORMAL;
			break;
		case 'W':   // Linewrap off
			set_bit(FLAG_WRAP_OFF, &(lcddev.flags));
			input_state = NORMAL;
			break;
		case 'X':   // character bypassing
			input_state = CHAR_BYPAS;
			break;
		default:
			PINFO( "unrecognized escape sequence: %#x ('%c')\n", input, input );
			input_state = NORMAL;
			break;
		} /* case ESC switch(input) */
		break;
	case DCA_Y :
		if ( input < DISP_ROWS ) {
			row = input;
		} else {
			PINFO( "tried to set cursor to off screen location\n" );
			row = DISP_ROWS - 1;
		}
		input_state = DCA_X;
		break;
	case DCA_X :
		if ( input < DISP_COLS ) {
			col = input;
		}  else {
			PINFO( "tried to set cursor to off screen location\n" );
			col = DISP_COLS - 1;
		}
		input_state = NORMAL;
		break;
	case CGRAM_SELECT_ALL :
		if( input > '7' || input < '0' ) {
			PINFO( "Bad CGRAM index %d\n", (int)(input-'0') );
			input_state = NORMAL;
		} else {
			cgram_index = input - '0';
			cgram_row_count = 0;
			input_state = CGRAM_GENERATE_ALL;
		}
		break;
	case CGRAM_GENERATE_ALL :
		cgram_pixels[cgram_row_count] = input;
		++cgram_row_count;
		if( cgram_row_count != 8 ) 
			break; /* wait for 8 characters */
		write_CGRAM_all(cgram_index, cgram_pixels);
		input_state = NORMAL;
		break;
	case CGRAM_SELECT_ONE :
		if( input > '7' || input < '0' ) {
			PINFO( "LCD: Bad CGRAM index %d\n", (int)(input-'0') );
			input_state = NORMAL;
		} else {
			cgram_index = input - '0';
			input_state = CGRAM_SELECT_LCD_ONE;
		}
		break;
	case CGRAM_SELECT_LCD_ONE : /* input is in unsigned int!!! */
		if( (unsigned int)input >= NUM_CONTROLLERS) {
			PINFO( "LCD: CGRAM lcd selected out of range: %c\n", input );
			input_state = NORMAL;
		} else {
			cgram_lcd = input;
			cgram_row_count = 0;
			input_state = CGRAM_GENERATE_ONE;
		}
		break;
	case CGRAM_GENERATE_ONE :
		cgram_pixels[cgram_row_count] = input;
		++cgram_row_count;
		if( cgram_row_count != 8 ) break; /* wait for 8 characters */
		/* flush cgram only if it changes */
		write_CGRAM(cgram_lcd, cgram_index, cgram_pixels );
		input_state = NORMAL;
		break;
	case CHAR_BYPAS :
		global_write_DDRAM( input );
		input_state = NORMAL;
		break;
	default:
		PINFO("Unknown signal received\n");
		input_state = NORMAL;
	} /* siwtch(input_state) */
}

static ssize_t lcdmod_write(struct file *flip, const char __user *gdata, size_t length, loff_t *pos)
{
	int i;
	/* we do not support nonblocking writing */
	if (flip->f_flags & O_NONBLOCK ) {
		/* we dont have buffer, so we dont support nonblokcing writing */
		return -EFAULT;
	}
	if (!gdata) return 0;
	if ( test_bit(FLAG_CHARMAP_DISABLED, &lcddev.flags) ) {
		for(i = 0; i < length; i++ )
			PDEBUG(" cdev write: %c", gdata[i]);
			global_write_DDRAM( gdata[ i ] );
	} else {
		for(i = 0; i < length; i++ ) {
			PDEBUG(" cdev write: %c", gdata[i]);
			input_escape_sequence( gdata[ i ] );
		}
	}
	return length;
}

static inline short int check_irq_flag(void)
{
	return (test_bit(FLAG_WAS_IRQ, &lcddev.flags));
}

static ssize_t lcdmod_read_direct(struct file *flip, char __user *gdata, size_t count, loff_t *pos)
{
	if (flip->f_flags & O_NONBLOCK) {
		count = min((unsigned int)count, (unsigned int)(DISP_ROWS*DISP_COLS));
		if (copy_to_user(gdata, &state, count))
			return -EFAULT;
		return count;
	} else {
		int i, buff;
		for( i = 0; i < count; i++ ) {
			buff = global_read_DDRAM();
			if(copy_to_user(gdata, &buff, 1))
				return -EFAULT;
		}
		return count;
	}
}

static ssize_t lcdmod_read_nondirect(struct file *flip, char __user *gdata, size_t count, loff_t *pos)
{
	char state[2];
	state[0] = check_irq_flag();
	if (state[0] < 0) {
		if (flip->f_flags & O_NONBLOCK) {
			PDEBUG("read: noblock, and no new events\n");
			return -EAGAIN;
		} else {
			PDEBUG("read: block \n");
			wait_event_interruptible(irq_wait, (state[0] = check_irq_flag()));
		}
	}
	clear_bit(FLAG_WAS_IRQ, &(lcddev.flags));
	state[1] = lcddev.status;
	count =	min(2, (int)count);
	if (copy_to_user(gdata, &state, count))
		count = -EFAULT;
	return count;

}

static ssize_t lcdmod_read(struct file *flip, char __user *gdata, size_t count, loff_t *pos)
{
	if (flip->f_flags & O_DIRECT) {
		/* reading char from lcds */
		return lcdmod_read_direct(flip, gdata, count, pos);
	} else { 
		/* irq checking */
		return lcdmod_read_nondirect(flip, gdata, count, pos);
	}
}

static unsigned int lcdmod_poll(struct file *flip, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	PDEBUG("poll invocated \n");
	poll_wait(flip, &irq_wait, wait);
	if (check_irq_flag())
		mask |= POLLIN | POLLRDNORM;
	return mask;
}

static long lcdmod_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
	int retval = 0;
	char buff;
	struct lcdmod_cgram_t lcdmod_cgram;

	if ( !capable (CAP_SYS_ADMIN) ) return -EPERM; /** y need to be admin to do this */
	if ( _IOC_TYPE(cmd) != LCDMOD_IOC_MAGIC ) return -ENOTTY;
	if ( _IOC_NR(cmd) > LCDMOD_IOC_MAXNR ) return -ENOTTY;
	if ( _IOC_DIR(cmd) & _IOC_READ)
		if ( !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)) ) return -EFAULT;
	if (_IOC_DIR(cmd) & _IOC_WRITE)
		if ( !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)) ) return -EFAULT;

	switch( cmd ) {
	case LCDMOD_CGRAM_ONELCD:
		retval = __copy_from_user((void *)&lcdmod_cgram, (void *)arg, sizeof(lcdmod_cgram));
		if (lcdmod_cgram.ctrl_nr >= NUM_CONTROLLERS) return -EINVAL;
		if (lcdmod_cgram.cgram_index >= 8 ) return -EINVAL;
		write_CGRAM(lcdmod_cgram.ctrl_nr, lcdmod_cgram.cgram_index, lcdmod_cgram.cgram );
		break;
	case LCDMOD_CGRAM_ALLLCD:
		retval = __copy_from_user((void *)&lcdmod_cgram, (void *)arg, sizeof(struct lcdmod_cgram_t));
		if (lcdmod_cgram.cgram_index >= 8 ) return -EINVAL;
		write_CGRAM_all(lcdmod_cgram.cgram_index, lcdmod_cgram.cgram );
		break;
	case LCDMOD_SETLINEWRAP:
		break;
	case LCDMOD_GETLINEWRAP:
		break;
	case LCDMOD_BYPASS_DATA:
		retval = __get_user(buff, (char __user *)arg);
		global_write_DDRAM(buff);
		break;
	case LCDMOD_BYPASS_INST:
		retval = __get_user(buff, (char __user *)arg);
		write_inst(row / ROWS_PER_CTRL, buff);
		break;
	case LCDMOD_GETSTATUS:
		retval = parport_pc_read_status(lcddev.pd->port);
		break;
	case LCDMOD_WAS_IRQ:
		retval = check_irq_flag();
		break;
	case LCDMOD_CHARMAP_ENABLED:
		if ( arg )
			clear_bit(FLAG_CHARMAP_DISABLED, &lcddev.flags);
		else
			set_bit(FLAG_CHARMAP_DISABLED, &lcddev.flags);
		break;
	default:
		return -ENOTTY;
	}
	return retval;
}

static int lcdmod_open(struct inode *minode, struct file *filp )
{
	PDEBUG("open wcnt=%d rcnt=%d\n",lcdmod_write_open,lcdmod_read_open);
	if (filp->f_mode & FMODE_READ) {
		if (!lcdmod_read_open) 
			lcdmod_read_open++;
		else 
			return -EBUSY;
	}
	if (filp->f_mode & FMODE_WRITE) {
		if (!lcdmod_write_open) 
			lcdmod_write_open++;
		else 
			return -EBUSY;
	}
	return nonseekable_open(minode, filp );
}

static int lcdmod_release(struct inode *minode, struct file *flip )
{
	PDEBUG("close wcnt=%d rcnt=%d\n",lcdmod_write_open,lcdmod_read_open);
	if (flip->f_mode & FMODE_READ)
		lcdmod_read_open--;
	if (flip->f_mode & FMODE_WRITE)
		lcdmod_write_open--;
	if (asyncqueue) {
		fasync_helper(-1, flip, 0, &asyncqueue);
	}
	return 0;
}

static int lcdmod_fasync(int fd, struct file *flip, int mode)
{
	PDEBUG(" fasync: \n");
	fasync_helper(fd, flip, mode, &asyncqueue);
	parport_enable_irq(lcddev.pd->port);
	return 1; /* return number of ports initialized */
}

static const struct file_operations lcdmod_fops = {
		.owner = THIS_MODULE,
		.llseek = no_llseek,
		.write = lcdmod_write,
		.read = lcdmod_read,
		.poll = lcdmod_poll,
		.unlocked_ioctl = lcdmod_ioctl,
		.open = lcdmod_open,
		.release = lcdmod_release,
		.fasync = lcdmod_fasync
};
